<template>
  <div ref="containerRef" class="ht-flow-container"></div>
</template>

<script>
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'

export default {
  name: 'HTFlow',
  props: {
    // 边流量（单位由 unit 指定），四段：原料仓->输送带->混合机->回转窑->成品库
    flows: {
      type: Object,
      default: () => ({
        rawToConveyor: 357.075,
        conveyorToMixer: 284.39,
        mixerToKiln: 641.465,
        kilnToStorage: 520.0
      })
    },
    unit: { type: String, default: 'kW' },
    decimals: { type: Number, default: 3 },
    // 五个节点的图片：{ warehouse, conveyor, factory, kiln, storage }
    images: {
      type: Object,
      default: () => ({})
    },
    // 线路样式：'orthogonal' 或 'bezier'
    lineStyle: { type: String, default: 'orthogonal' },
    // 整体节点缩放
    nodeScale: { type: Number, default: 1.35 },
    // 节点位置（0~1 的百分比坐标）
    nodePositions: {
      type: Object,
      default: () => ({
        rawMaterial: { x: 0.28, y: 0.10 },
        conveyor:   { x: 0.72, y: 0.12 },
        mixer:      { x: 0.68, y: 0.40 },
        kiln:       { x: 0.32, y: 0.70 },
        storage:    { x: 0.68, y: 0.88 }
      })
    },
    // 每条边的样式类型：straight/orthogonal/bezier
    connectionTypes: {
      type: Object,
      default: () => ({
        rawToConveyor: 'straight',
        conveyorToMixer: 'straight',
        mixerToKiln: 'straight',
        kilnToStorage: 'straight'
      })
    },
    // 连线主色（默认科技红）
    lineColor: { type: String, default: '#ed3f35' },
    showAnchors: { type: Boolean, default: true }
  },
  setup(props) {
    const containerRef = ref(null)
    let canvas = null
    let ctx = null
    let animationTask = null
    let time = 0
    
    // 视图与世界坐标：支持纵向高比宽的透视布局与自适应缩放
    const world = { width: 1000, height: 1600 }
    const view = { scale: 1, offsetX: 0, offsetY: 0, padding: 40 }
    const toCanvas = (p) => ({
      x: view.offsetX + p.x * view.scale,
      y: view.offsetY + p.y * view.scale
    })
    const updateTransform = () => {
      if (!canvas) return
      const usableW = Math.max(1, canvas.width - view.padding * 2)
      const usableH = Math.max(1, canvas.height - view.padding * 2)
      const s = Math.min(usableW / world.width, usableH / world.height)
      view.scale = s
      view.offsetX = (canvas.width - world.width * s) / 2
      view.offsetY = (canvas.height - world.height * s) / 2
    }

    // 可选贴图（若存在 public/buildings 下的图片则自动使用）
    const textures = { warehouse: null, conveyor: null, factory: null, kiln: null, storage: null }
    const textureCandidates = {
      warehouse: ['/buildings/warehouse.png', '/buildings/warehouse.jpg'],
      conveyor: ['/buildings/conveyor.png', '/buildings/conveyor.jpg'],
      factory: ['/buildings/factory.png', '/buildings/factory.jpg'],
      kiln: ['/buildings/kiln.png', '/buildings/kiln.jpg'],
      storage: ['/buildings/storage.png', '/buildings/storage.jpg']
    }
    const tryLoad = (url) => new Promise((resolve) => {
      const img = new Image()
      img.crossOrigin = 'anonymous'
      img.onload = () => resolve(img)
      img.onerror = () => resolve(null)
      img.src = url
    })
    const loadTexturesFromProps = async () => {
      const entries = Object.entries(props.images || {})
      for (const [key, url] of entries) {
        if (!url) continue
        const img = await tryLoad(url)
        if (img) textures[key] = img
      }
    }
    const loadTextures = async () => {
      // 优先使用 props.images
      await loadTexturesFromProps()
      // 若某些键未提供，则尝试 public/buildings 下的候选文件
      for (const key of Object.keys(textureCandidates)) {
        if (textures[key]) continue
        const list = textureCandidates[key]
        for (let i = 0; i < list.length; i++) {
          const img = await tryLoad(list[i])
          if (img) { textures[key] = img; break }
        }
      }
    }
    
    // 节点：纵向布局（高比宽），轻微左右摆动以形成曲折感
    const nodes = [
      {
        id: 'rawMaterial', name: '原料仓',
        x: 0, y: 0,
        color: '#4c9bfd', buildingType: 'warehouse', pulse: 0
      },
      {
        id: 'conveyor', name: '输送带',
        x: 0, y: 0,
        color: '#9fe6b8', buildingType: 'conveyor', pulse: 0.5
      },
      {
        id: 'mixer', name: '混合机',
        x: 0, y: 0,
        color: '#00f2f1', buildingType: 'factory', pulse: 1.0
      },
      {
        id: 'kiln', name: '回转窑',
        x: 0, y: 0,
        color: '#ed3f35', buildingType: 'kiln', pulse: 1.5
      },
      {
        id: 'storage', name: '成品库',
        x: 0, y: 0,
        color: '#006cff', buildingType: 'storage', pulse: 2.0
      }
    ]

    const applyNodePositions = () => {
      const pos = props.nodePositions || {}
      nodes.forEach(n => {
        const p = pos[n.id] || pos[
          n.id === 'rawMaterial' ? 'rawMaterial' :
          n.id === 'conveyor' ? 'conveyor' :
          n.id === 'mixer' ? 'mixer' :
          n.id === 'kiln' ? 'kiln' : 'storage'
        ]
        if (p) {
          n.x = world.width * p.x
          n.y = world.height * p.y
        }
      })
    }

    // 连接：按节点纵向生成曲折的下行路径
    const connections = [
      { from: 0, to: 1, particles: [], path: [], valueKey: 'rawToConveyor', value: 0 },
      { from: 1, to: 2, particles: [], path: [], valueKey: 'conveyorToMixer', value: 0 },
      { from: 2, to: 3, particles: [], path: [], valueKey: 'mixerToKiln', value: 0 },
      { from: 3, to: 4, particles: [], path: [], valueKey: 'kilnToStorage', value: 0 }
    ]

    const updateConnectionValues = () => {
      connections.forEach(c => {
        const v = Number(props.flows?.[c.valueKey] ?? 0)
        c.value = Number.isFinite(v) ? v : 0
      })
      // 根据流量调整粒子速度/数量
      connections.forEach(c => {
        const norm = Math.min(1, Math.max(0, c.value / (Math.max(...[props.flows.rawToConveyor, props.flows.conveyorToMixer, props.flows.mixerToKiln, props.flows.kilnToStorage].map(Number).filter(v => Number.isFinite(v) && v > 0), 1))))
        c.particles.forEach(p => {
          p.speed = 0.004 + norm * 0.004 + Math.random() * 0.0015
          p.opacity = 0.6 + norm * 0.35
          p.size = 3 + norm * 2
        })
      })
    }

    const computePaths = () => {
      const makeOrthogonal = (a, b) => {
        const p0 = { x: nodes[a].x, y: nodes[a].y + 42 }
        const p3 = { x: nodes[b].x, y: nodes[b].y - 42 }
        const midY = p0.y + (p3.y - p0.y) * 0.5
        const xPad = world.width * 0.06 * (a % 2 === 0 ? 1 : -1)
        return [
          p0,
          { x: p0.x, y: midY },
          { x: p3.x + xPad, y: midY },
          p3
        ]
      }
      const makeBezier = (a, b) => {
        const bendBase = Math.min(0.18, Math.max(0.08, (canvas ? canvas.width / Math.max(canvas.height, 1) : 1) * 0.12))
        const p0 = { x: nodes[a].x, y: nodes[a].y + 40 }
        const p3 = { x: nodes[b].x, y: nodes[b].y - 40 }
        const dy = p3.y - p0.y
        const midY1 = p0.y + dy * 0.35
        const midY2 = p0.y + dy * 0.7
        const xOffset1 = (a % 2 === 0 ? 1 : -1) * world.width * bendBase
        const xOffset2 = (a % 2 === 0 ? -1 : 1) * world.width * bendBase * 0.6
        return [
          p0,
          { x: p0.x + xOffset1, y: midY1 },
          { x: p3.x + xOffset2, y: midY2 },
          p3
        ]
      }
      const makeStraight = (a, b) => {
        const p0 = { x: nodes[a].x, y: nodes[a].y + 42 }
        const p3 = { x: nodes[b].x, y: nodes[b].y - 42 }
        return [p0, p3]
      }

      const typeFor = (c) => props.connectionTypes?.[c] || props.lineStyle
      connections[0].path = (typeFor('rawToConveyor') === 'orthogonal') ? makeOrthogonal(0,1)
                           : (typeFor('rawToConveyor') === 'bezier') ? makeBezier(0,1)
                           : makeStraight(0,1)
      connections[1].path = (typeFor('conveyorToMixer') === 'orthogonal') ? makeOrthogonal(1,2)
                           : (typeFor('conveyorToMixer') === 'bezier') ? makeBezier(1,2)
                           : makeStraight(1,2)
      connections[2].path = (typeFor('mixerToKiln') === 'orthogonal') ? makeOrthogonal(2,3)
                           : (typeFor('mixerToKiln') === 'bezier') ? makeBezier(2,3)
                           : makeStraight(2,3)
      connections[3].path = (typeFor('kilnToStorage') === 'orthogonal') ? makeOrthogonal(3,4)
                           : (typeFor('kilnToStorage') === 'bezier') ? makeBezier(3,4)
                           : makeStraight(3,4)
    }

    const init = () => {
      canvas = document.createElement('canvas')
      ctx = canvas.getContext('2d')
      containerRef.value.appendChild(canvas)
      
      resizeCanvas()
      updateTransform()
      applyNodePositions()
      computePaths()
      loadTextures() // 异步加载贴图（存在即用）
      initParticles()
      startAnimation()
      
      window.addEventListener('resize', resizeCanvas)
    }

    const resizeCanvas = () => {
      if (!canvas || !containerRef.value) return
      
      const rect = containerRef.value.getBoundingClientRect()
      const dpr = window.devicePixelRatio || 1
      canvas.width = Math.floor(rect.width * dpr)
      canvas.height = Math.floor(rect.height * dpr)
      canvas.style.width = rect.width + 'px'
      canvas.style.height = rect.height + 'px'
      updateTransform()
      computePaths()
    }

    const initParticles = () => {
      connections.forEach(conn => {
        conn.particles = []
        for (let i = 0; i < 4; i++) {
          conn.particles.push({
            progress: i * 0.25,
            speed: 0.005 + Math.random() * 0.002,
            size: 3.2 + Math.random() * 1.6,
            opacity: 0.7 + Math.random() * 0.25
          })
        }
      })
      updateConnectionValues()
    }

    // 绘制3D建筑风格的节点
    const drawSpriteBuilding = (x, y, size, img) => {
      const w = size * 2.2
      const h = size * 1.4
      ctx.save()
      ctx.translate(x, y - h * 0.55)
      // 轻微透视倾斜
      ctx.transform(1, 0, -0.15, 1, 0, 0)
      ctx.shadowColor = 'rgba(0,0,0,0.45)'
      ctx.shadowBlur = 12
      ctx.drawImage(img, -w/2, -h, w, h)
      ctx.restore()
    }

    const drawBuilding = (node) => {
      const pos = toCanvas({ x: node.x, y: node.y })
      const pulseEffect = Math.sin(time * 1.5 + node.pulse) * 0.15 + 0.85
      const yNorm = node.y / world.height
      const perspectiveScale = 0.9 + 0.5 * yNorm // 越靠下越大，形成纵深
      const baseSize = 52 * props.nodeScale * view.scale * perspectiveScale * pulseEffect
      
      // 发光底座（加大尺寸）
      drawGlowBase(pos.x, pos.y + 30 * view.scale, baseSize * 2.2, node.color)
      
      // 贴图优先，缺失则用几何绘制
      const tex = textures[node.buildingType]
      if (tex) {
        drawSpriteBuilding(pos.x, pos.y, baseSize, tex)
      } else {
        switch(node.buildingType) {
          case 'warehouse':
            drawWarehouse(pos.x, pos.y, baseSize, node.color)
            break
          case 'conveyor':
            drawConveyor(pos.x, pos.y, baseSize, node.color)
            break
          case 'factory':
            drawFactory(pos.x, pos.y, baseSize, node.color)
            break
          case 'kiln':
            drawKiln(pos.x, pos.y, baseSize, node.color)
            break
          case 'storage':
            drawStorage(pos.x, pos.y, baseSize, node.color)
            break
        }
      }
      
      // 标签
      drawLabel(pos.x, pos.y + 52 * view.scale, node.name)
    }

    // 发光底座
    const drawGlowBase = (x, y, size, color) => {
      // 外层发光
      const outerGradient = ctx.createRadialGradient(x, y, 0, x, y, size)
      outerGradient.addColorStop(0, color + '40')
      outerGradient.addColorStop(0.6, color + '20')
      outerGradient.addColorStop(1, color + '00')
      
      ctx.fillStyle = outerGradient
      ctx.beginPath()
      ctx.ellipse(x, y, size, size * 0.3, 0, 0, Math.PI * 2)
      ctx.fill()
      
      // 内层底座
      const innerGradient = ctx.createRadialGradient(x, y, 0, x, y, size * 0.6)
      innerGradient.addColorStop(0, color + '80')
      innerGradient.addColorStop(0.8, color + '40')
      innerGradient.addColorStop(1, color + '20')
      
      ctx.fillStyle = innerGradient
      ctx.beginPath()
      ctx.ellipse(x, y, size * 0.6, size * 0.18, 0, 0, Math.PI * 2)
      ctx.fill()
    }

    // 仓库建筑
    const drawWarehouse = (x, y, size, color) => {
      const width = size * 1.2
      const height = size * 0.8
      const depth = size * 0.4
      
      // 主体
      draw3DBox(x - width/2, y - height, width, height, depth, color, '#ffffff')
      
      // 屋顶
      ctx.fillStyle = darkenColor(color, 0.3)
      ctx.beginPath()
      ctx.moveTo(x - width/2 - depth/2, y - height - depth/2)
      ctx.lineTo(x + width/2 - depth/2, y - height - depth/2)
      ctx.lineTo(x + width/2 + depth/2, y - height + depth/2)
      ctx.lineTo(x - width/2 + depth/2, y - height + depth/2)
      ctx.closePath()
      ctx.fill()
    }

    // 输送带
    const drawConveyor = (x, y, size, color) => {
      const width = size * 1.5
      const height = size * 0.3
      const depth = size * 0.2
      
      // 输送带主体
      draw3DBox(x - width/2, y - height/2, width, height, depth, color, '#ffffff')
      
      // 支撑柱
      for (let i = 0; i < 3; i++) {
        const pillarX = x - width/2 + (i * width/2)
        draw3DBox(pillarX - 2, y, 4, size * 0.4, 4, darkenColor(color, 0.4), '#ffffff')
      }
    }

    // 工厂
    const drawFactory = (x, y, size, color) => {
      const width = size
      const height = size * 1.2
      const depth = size * 0.5
      
      // 主建筑
      draw3DBox(x - width/2, y - height, width, height, depth, color, '#ffffff')
      
      // 烟囱
      const chimneyWidth = size * 0.15
      draw3DBox(x + width/4, y - height - size * 0.6, chimneyWidth, size * 0.6, chimneyWidth, darkenColor(color, 0.5), '#ffffff')
      
      // 烟雾效果
      drawSmoke(x + width/4, y - height - size * 0.6)
    }

    // 回转窑
    const drawKiln = (x, y, size, color) => {
      const width = size * 1.3
      const height = size * 0.6
      const depth = size * 0.6
      
      // 窑体（圆柱形效果）
      draw3DCylinder(x, y - height/2, width, height, color)
      
      // 火焰效果
      drawFlame(x + width/2, y - height/2)
    }

    // 成品库
    const drawStorage = (x, y, size, color) => {
      const width = size * 1.1
      const height = size * 1.1
      const depth = size * 0.6
      
      // 多个储存罐
      for (let i = 0; i < 2; i++) {
        for (let j = 0; j < 2; j++) {
          const tankX = x - width/4 + i * width/2
          const tankY = y - height/2 - j * height/3
          const tankSize = size * 0.4
          draw3DCylinder(tankX, tankY, tankSize * 0.8, tankSize, lightenColor(color, i * 0.1 + j * 0.1))
        }
      }
    }

    // 3D盒子
    const draw3DBox = (x, y, width, height, depth, color, edgeColor = '#ffffff') => {
      // 前面
      ctx.fillStyle = color
      ctx.fillRect(x, y, width, height)
      
      // 顶面
      ctx.fillStyle = lightenColor(color, 0.3)
      ctx.beginPath()
      ctx.moveTo(x, y)
      ctx.lineTo(x + depth, y - depth)
      ctx.lineTo(x + width + depth, y - depth)
      ctx.lineTo(x + width, y)
      ctx.closePath()
      ctx.fill()
      
      // 右侧面
      ctx.fillStyle = darkenColor(color, 0.2)
      ctx.beginPath()
      ctx.moveTo(x + width, y)
      ctx.lineTo(x + width + depth, y - depth)
      ctx.lineTo(x + width + depth, y + height - depth)
      ctx.lineTo(x + width, y + height)
      ctx.closePath()
      ctx.fill()
      
      // 边框
      ctx.strokeStyle = edgeColor + '60'
      ctx.lineWidth = 1
      ctx.strokeRect(x, y, width, height)
    }

    // 3D圆柱
    const draw3DCylinder = (x, y, width, height, color) => {
      // 圆柱体侧面
      const gradient = ctx.createLinearGradient(x - width/2, y, x + width/2, y)
      gradient.addColorStop(0, darkenColor(color, 0.3))
      gradient.addColorStop(0.3, color)
      gradient.addColorStop(0.7, color)
      gradient.addColorStop(1, darkenColor(color, 0.3))
      
      ctx.fillStyle = gradient
      ctx.fillRect(x - width/2, y - height/2, width, height)
      
      // 顶部椭圆
      ctx.fillStyle = lightenColor(color, 0.2)
      ctx.beginPath()
      ctx.ellipse(x, y - height/2, width/2, width/8, 0, 0, Math.PI * 2)
      ctx.fill()
    }

    // 烟雾效果
    const drawSmoke = (x, y) => {
      for (let i = 0; i < 3; i++) {
        const smokeY = y - i * 8 - Math.sin(time * 2 + i) * 3
        const smokeX = x + Math.sin(time + i * 0.5) * 2
        const opacity = (3 - i) * 0.2
        
        ctx.fillStyle = `rgba(200, 200, 200, ${opacity})`
        ctx.beginPath()
        ctx.arc(smokeX, smokeY, 3 + i, 0, Math.PI * 2)
        ctx.fill()
      }
    }

    // 火焰效果
    const drawFlame = (x, y) => {
      const flameHeight = 8 + Math.sin(time * 4) * 3
      const gradient = ctx.createLinearGradient(x, y, x, y - flameHeight)
      gradient.addColorStop(0, '#ff6b35')
      gradient.addColorStop(0.5, '#ff9500')
      gradient.addColorStop(1, '#ffff0080')
      
      ctx.fillStyle = gradient
      ctx.beginPath()
      ctx.moveTo(x - 3, y)
      ctx.quadraticCurveTo(x, y - flameHeight, x + 3, y)
      ctx.closePath()
      ctx.fill()
    }

    // 标签
    const drawLabel = (x, y, text) => {
      ctx.fillStyle = '#ffffff'
      ctx.font = 'bold 12px Arial'
      ctx.textAlign = 'center'
      ctx.textBaseline = 'middle'
      
      // 文字阴影
      ctx.shadowColor = 'rgba(0,0,0,0.5)'
      ctx.shadowBlur = 3
      ctx.shadowOffsetX = 1
      ctx.shadowOffsetY = 1
      
      ctx.fillText(text, x, y)
      
      // 重置阴影
      ctx.shadowColor = 'transparent'
      ctx.shadowBlur = 0
      ctx.shadowOffsetX = 0
      ctx.shadowOffsetY = 0
    }

    // 绘制曲折的连接线
    const drawConnection = (conn) => {
      const path = conn.path.map(toCanvas)
      
      const start = path[0]
      const end = path[path.length - 1]
      const gradient = ctx.createLinearGradient(start.x, start.y, end.x, end.y)
      const c = props.lineColor
      gradient.addColorStop(0, hexToRgba(c, 0.4))
      gradient.addColorStop(0.5, hexToRgba(c, 1))
      gradient.addColorStop(1, hexToRgba(c, 0.6))

      // 底层粗线，营造管道质感
      ctx.strokeStyle = hexToRgba(c, 0.18)
      ctx.lineWidth = Math.max(2, 6 * view.scale)
      ctx.shadowColor = hexToRgba(c, 0.35)
      ctx.shadowBlur = 6
      ctx.beginPath()
      ctx.moveTo(path[0].x, path[0].y)
      if (props.lineStyle === 'bezier') {
        for (let i = 1; i < path.length; i++) {
          if (i === path.length - 1) ctx.lineTo(path[i].x, path[i].y)
          else {
            const cp1x = path[i-1].x + (path[i].x - path[i-1].x) * 0.5
            const cp1y = path[i-1].y + (path[i].y - path[i-1].y) * 0.5
            const cp2x = path[i].x - (path[i+1].x - path[i].x) * 0.3
            const cp2y = path[i].y - (path[i+1].y - path[i].y) * 0.3
            ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, path[i].x, path[i].y)
          }
        }
      } else {
        for (let i = 1; i < path.length; i++) ctx.lineTo(path[i].x, path[i].y)
      }
      ctx.stroke()

      // 顶层细线，使用渐变更精致
      ctx.strokeStyle = gradient
      ctx.lineWidth = Math.max(1, 2.2 * view.scale)
      ctx.shadowColor = hexToRgba(c, 1)
      ctx.shadowBlur = 10
      ctx.beginPath()
      ctx.moveTo(path[0].x, path[0].y)
      if (props.lineStyle === 'bezier') {
        for (let i = 1; i < path.length; i++) {
          if (i === path.length - 1) ctx.lineTo(path[i].x, path[i].y)
          else {
            const cp1x = path[i-1].x + (path[i].x - path[i-1].x) * 0.5
            const cp1y = path[i-1].y + (path[i].y - path[i-1].y) * 0.5
            const cp2x = path[i].x - (path[i+1].x - path[i].x) * 0.3
            const cp2y = path[i].y - (path[i+1].y - path[i].y) * 0.3
            ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, path[i].x, path[i].y)
          }
        }
      } else {
        for (let i = 1; i < path.length; i++) ctx.lineTo(path[i].x, path[i].y)
      }
      ctx.stroke()

      // 顶层虚线流动效果
      ctx.save()
      ctx.strokeStyle = '#cfffff'
      ctx.lineWidth = Math.max(1, 1.8 * view.scale)
      ctx.setLineDash([14 * view.scale, 12 * view.scale])
      ctx.lineDashOffset = -time * 40
      ctx.beginPath()
      ctx.moveTo(path[0].x, path[0].y)
      if (props.lineStyle === 'bezier') {
        for (let i = 1; i < path.length; i++) {
          if (i === path.length - 1) ctx.lineTo(path[i].x, path[i].y)
          else {
            const cp1x = path[i-1].x + (path[i].x - path[i-1].x) * 0.5
            const cp1y = path[i-1].y + (path[i].y - path[i-1].y) * 0.5
            const cp2x = path[i].x - (path[i+1].x - path[i].x) * 0.3
            const cp2y = path[i].y - (path[i+1].y - path[i].y) * 0.3
            ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, path[i].x, path[i].y)
          }
        }
      } else {
        for (let i = 1; i < path.length; i++) ctx.lineTo(path[i].x, path[i].y)
      }
      ctx.stroke()
      ctx.restore()

      // 箭头更细更亮
      const lastPoint = path[path.length - 1]
      const secondLastPoint = path[path.length - 2]
      const angle = Math.atan2(lastPoint.y - secondLastPoint.y, lastPoint.x - secondLastPoint.x)
      drawArrow(lastPoint.x - 18 * view.scale, lastPoint.y, angle, hexToRgba(c, 1))
      ctx.shadowColor = 'transparent'
      ctx.shadowBlur = 0

      // 沿线光点（更美观的点缀）
      const markerSpacing = 70 * view.scale
      let acc = 0
      for (let i = 0; i < path.length - 1; i++) {
        const a = path[i], b = path[i+1]
        const segLen = Math.hypot(b.x - a.x, b.y - a.y)
        for (let d = acc; d < segLen; d += markerSpacing) {
          const t = d / segLen
          const mx = a.x + (b.x - a.x) * t
          const my = a.y + (b.y - a.y) * t
          const r = 2.6 * view.scale
          const g2 = ctx.createRadialGradient(mx, my, 0, mx, my, 6 * view.scale)
          g2.addColorStop(0, '#cfffff')
          g2.addColorStop(0.5, '#00f2f180')
          g2.addColorStop(1, 'transparent')
          ctx.fillStyle = g2
          ctx.beginPath()
          ctx.arc(mx, my, r, 0, Math.PI * 2)
          ctx.fill()
        }
        acc = (segLen + acc) % markerSpacing
      }

      // 流动粒子（保留）
      conn.particles.forEach(particle => {
        const pathLength = conn.path.length - 1
        const segmentIndex = Math.floor(particle.progress * pathLength)
        const segmentProgress = (particle.progress * pathLength) % 1
        if (segmentIndex < pathLength) {
          const a = toCanvas(conn.path[segmentIndex])
          const b = toCanvas(conn.path[segmentIndex + 1])
          const x = a.x + (b.x - a.x) * segmentProgress
          const y = a.y + (b.y - a.y) * segmentProgress
          const particleGradient = ctx.createRadialGradient(x, y, 0, x, y, particle.size * 2 * view.scale)
          particleGradient.addColorStop(0, '#00f2f1')
          particleGradient.addColorStop(0.5, '#00f2f180')
          particleGradient.addColorStop(1, '#00f2f100')
          ctx.fillStyle = particleGradient
          ctx.beginPath()
          ctx.arc(x, y, particle.size * view.scale, 0, Math.PI * 2)
          ctx.fill()
        }
        particle.progress += particle.speed
        if (particle.progress > 1) particle.progress = 0
      })

      // 中部数值标签
      const mid = getPathMiddlePoint(path)
      if (mid) {
        const label = formatValue(conn.value, props.unit, props.decimals)
        drawEdgeLabel(mid.x, mid.y - 10 * view.scale, label)
      }
    }

    const drawAnchor = (x, y, color) => {
      ctx.save()
      ctx.fillStyle = color
      ctx.shadowColor = color
      ctx.shadowBlur = 6
      ctx.beginPath()
      ctx.arc(x, y, 4.5 * view.scale, 0, Math.PI * 2)
      ctx.fill()
      ctx.restore()
    }

    const getPathMiddlePoint = (path) => {
      if (!path || path.length < 2) return null
      // 计算路径总长并取中点
      const segLens = []
      let total = 0
      for (let i = 0; i < path.length - 1; i++) {
        const l = Math.hypot(path[i+1].x - path[i].x, path[i+1].y - path[i].y)
        segLens.push(l); total += l
      }
      let target = total / 2
      for (let i = 0; i < segLens.length; i++) {
        if (target <= segLens[i]) {
          const t = target / segLens[i]
          return {
            x: path[i].x + (path[i+1].x - path[i].x) * t,
            y: path[i].y + (path[i+1].y - path[i].y) * t
          }
        }
        target -= segLens[i]
      }
      return path[Math.floor(path.length/2)]
    }

    const drawEdgeLabel = (x, y, text) => {
      ctx.save()
      ctx.font = 'bold 11px Arial'
      ctx.textAlign = 'center'
      ctx.textBaseline = 'middle'
      ctx.fillStyle = '#cfe8ff'
      ctx.shadowColor = 'rgba(0,108,255,0.6)'
      ctx.shadowBlur = 6
      ctx.fillText(text, x, y)
      ctx.restore()
    }

    const formatValue = (v, unit, decimals) => {
      if (!Number.isFinite(v)) return `-- ${unit || ''}`
      const f = new Intl.NumberFormat('en-US', { minimumFractionDigits: decimals, maximumFractionDigits: decimals }).format(v)
      return `${f}${unit ? ' ' + unit : ''}`
    }

    const drawArrow = (x, y, angle, color) => {
      ctx.save()
      ctx.translate(x, y)
      ctx.rotate(angle)
      
      ctx.fillStyle = color
      ctx.beginPath()
      ctx.moveTo(0, 0)
      ctx.lineTo(-12, -6)
      ctx.lineTo(-12, 6)
      ctx.closePath()
      ctx.fill()
      
      ctx.restore()
    }

    const lightenColor = (color, amount) => {
      const num = parseInt(color.replace('#', ''), 16)
      const r = Math.min(255, (num >> 16) + Math.round(255 * amount))
      const g = Math.min(255, ((num >> 8) & 0x00FF) + Math.round(255 * amount))
      const b = Math.min(255, (num & 0x0000FF) + Math.round(255 * amount))
      return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`
    }

    const darkenColor = (color, amount) => {
      const num = parseInt(color.replace('#', ''), 16)
      const r = Math.max(0, (num >> 16) - Math.round(255 * amount))
      const g = Math.max(0, ((num >> 8) & 0x00FF) - Math.round(255 * amount))
      const b = Math.max(0, (num & 0x0000FF) - Math.round(255 * amount))
      return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`
    }

    const hexToRgba = (hex, alpha = 1) => {
      const str = hex.replace('#', '')
      const num = parseInt(str, 16)
      const r = (num >> 16) & 0xff
      const g = (num >> 8) & 0xff
      const b = num & 0xff
      return `rgba(${r},${g},${b},${alpha})`
    }

    // 纵深地面与网格，增强立体感并向下延伸
    const drawDepthFloor = () => {
      const g = ctx.createLinearGradient(0, 0, 0, canvas.height)
      g.addColorStop(0, 'rgba(0,0,0,0)')
      g.addColorStop(0.55, 'rgba(0,108,255,0.05)')
      g.addColorStop(1, 'rgba(0,20,40,0.28)')
      ctx.fillStyle = g
      ctx.fillRect(0, 0, canvas.width, canvas.height)
      
      // 透视水平线（向下收拢），强调“往下延伸”的纵深
      ctx.strokeStyle = 'rgba(0,108,255,0.08)'
      ctx.lineWidth = 1
      const lines = 8
      for (let i = 0; i < lines; i++) {
        const t = i / lines
        const y = view.offsetY + t * world.height * view.scale
        const pad = (0.18 - t * 0.14) * canvas.width
        ctx.beginPath()
        ctx.moveTo(pad, y)
        ctx.lineTo(canvas.width - pad, y)
        ctx.stroke()
      }
    }

    const render = () => {
      if (!ctx || !canvas) return
      
      // 清空画布
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      drawDepthFloor()
      
      // 绘制连接线和粒子
      connections.forEach(drawConnection)
      
      // 绘制建筑
      nodes.forEach(drawBuilding)
      
      time += 0.02
    }

    const startAnimation = () => {
      const animate = () => {
        render()
        animationTask = requestAnimationFrame(animate)
      }
      animate()
    }

    onMounted(() => {
      init()
      updateConnectionValues()
    })

    watch(() => props.flows, () => {
      updateConnectionValues()
    }, { deep: true })

    watch(() => props.nodePositions, () => {
      applyNodePositions()
      computePaths()
    }, { deep: true })

    watch(() => props.connectionTypes, () => {
      computePaths()
    }, { deep: true })

    onBeforeUnmount(() => {
      if (animationTask) {
        cancelAnimationFrame(animationTask)
      }
      window.removeEventListener('resize', resizeCanvas)
    })

    return {
      containerRef
    }
  }
}
</script>

<style scoped>
.ht-flow-container {
  width: 100%;
  height: 100%;
  position: relative;
  background: transparent;
  border-radius: 8px;
  overflow: hidden;
}

.ht-flow-container canvas {
  display: block;
  border-radius: 8px;
}
</style>